#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <cstdlib>
#include <muParser/muParser.h>

#ifndef DRIFT_NO_MPI
  #include <mpi.h>
#endif

#include "omd/omdtool.h"
#include "omd/param.h"
#include "omd/piper.h"
#include "drift/drift.h"
#include "compile-id.inc"

#define ONROOT(CMD) {if(me==0) CMD; MPI_Barrier(MPI_COMM_WORLD);}

using std::ofstream;
using namespace omd;
using namespace edrift;

// --- local modules ---
#include "drift/domain.h"
#include "drift/buffer.h"
#include "plotter.h"

using edrift::Buffer;

#ifdef DRIFT_NO_MPI
char version_string[]="edrift 0.0 rel-01";
#else
char version_string[]="edrift 0.0 rel-01 MPI";
#endif

// ---------------------DriftServe

class DriftServe {
  int current;
  int currentplot;
  int me;
  
  string loopcmd;

  int looplevel; // stay with one level!
  int nloop;
  int loopcounter;

  bool loading;
  bool quiet;
  bool loopbreak;
  
  vector<TableReader*> table;
  vector<DriftDomain*> fd;
  vector<Buffer*> buffer;
  vector<DriftPlotter*> plotter;
  vector<string> macname;
  vector<string> macstring;

  void sayer(string msg) {
    //if(!quiet)
    std::cerr<<msg<<std::endl;
  }
  
  void say(string msg) {
    if(me==0) std::cout<<msg<<std::endl;
  }

#include "search.h"
#include "translate.h"  
#include "delete.h"  
#include "get.h"  

// ----------COMMAND FUNCTIONS------------
  
#include "cmd_create.h"
#include "cmd_plot.h"
#include "cmd_show.h"
#include "cmd_buffer.h"
#include "cmd_export.h"
#include "cmd_set.h"
#include "cmd_table.h"
#include "cmd_dump.h"
#include "cmd_load.h"
#include "cmd_step.h"
#include "cmd_loop.h"    
#include "cmd_fill.h"  
#include "cmd_del.h"
#include "cmd_reset.h"
#include "cmd_echo.h"
#include "cmd_system.h"
#include "cmd_macro.h"
  
  void cmd_sleep(istringstream& ss) {
    double n=1; ss>>n;
    usleep(int(n*1e6));
  }
    
  void cmd_use(istringstream& ss) {
    string sfd;
    ss>>sfd;
    int slc=search_fd(sfd);
    if(slc<0) throw "can not find fd domain";
    current=slc;
  }
    
  void cmd_ping(istringstream& ss) {
    string fname;
    if(ss>>fname) {
      ofstream ofl(fname.c_str());
      ofl<<"OK\n";
      ofl.close();
    } else std::cout<<"OK\n";
  }
    
// ----------COMMANDS END---------------
  
public:
  DriftServe() {
    looplevel=0;
    nloop=1;
    current=0;
    loading=false;
    quiet=true;
    loopbreak=false;
        
#ifdef DRIFT_NO_MPI
    me=0;
#else
    MPI_Comm_rank(MPI_COMM_WORLD,&me);
#endif
  }
  
  void cleanup() {
    for(int i=0;i<(int)fd.size();i++) delete fd[i];
    for(int i=0;i<(int)buffer.size();i++) delete buffer[i];
    for(int i=0;i<(int)table.size();i++) delete table[i];    
    for(int i=0;i<(int)plotter.size();i++) delete plotter[i];     
  }    
  
  ~DriftServe() {
    
  }
  
  int parse(string cmdpar) {
    int retval=1;
    int sep;
    vector<string> cmds;
    int pcmt=cmdpar.find('#');
    if(pcmt!=cmdpar.npos) cmdpar.erase(pcmt,cmdpar.npos);
    
    do {
      sep=cmdpar.find(";");
      cmds.push_back(cmdpar.substr(0,sep));
      cmdpar.erase(0,sep+1);
    } while(sep!=cmdpar.npos);
            
    for(int n=0;n<(int)cmds.size();n++) {
      if(looplevel) cmds[n]=string("loop ")+cmds[n];
      istringstream ss(cmds[n]);
      string cmd;
      if(!(ss>>cmd)) continue;
      
      // exec every defined interval in a loop
      // command@interval:stop
      
      if(loopcmd.size() && cmd.find('@')!=cmd.npos) {
        istringstream ccc(replace_char(cmd,"@:",' '));
        int ic;
        if(!(ccc>>cmd>>ic)) throw "invalid interval";
        if(loopcounter%ic) continue;
        if(ccc>>ic) if(loopcounter>ic) continue;
      }
      
      try {
        if(cmd=="create") cmd_create(ss);
        else if(cmd=="exit"||cmd=="quit"||cmd=="q") retval=0;
        else if(cmd=="inspect") cmd_inspect(); // implemented in cmd_step.h
        else if(cmd=="step") cmd_step(ss);
        else if(cmd=="reset") cmd_reset(ss);
        else if(cmd=="use") cmd_use(ss);
        else if(cmd=="del"||cmd=="delete") cmd_del(ss);
        else if(cmd=="set") cmd_set(ss);
        else if(cmd=="export") cmd_export(ss);
        else if(cmd=="fill") cmd_fill(ss);
        else if(cmd=="buffer"||cmd=="buf") cmd_buffer(ss);
        else if(cmd=="loop") cmd_loop(ss);
        else if(cmd=="load") cmd_load(ss);
        else if(cmd=="quiet") quiet=true;
        else if(cmd=="verbose") quiet=false;
        else if(cmd=="table") cmd_table(ss);
        else if(cmd=="exec") cmd_macro(ss);
        else if(cmd=="echo"||cmd=="print") cmd_echo(ss);
        else if(cmd=="show") {ONROOT(cmd_show(ss));}
        else if(cmd=="plot") {ONROOT(cmd_plot(ss));}
        else if(cmd=="sleep") {ONROOT(cmd_sleep(ss));}
        else if(cmd=="dump") {ONROOT(cmd_dump(ss));}
        else if(cmd=="ping") {ONROOT(cmd_ping(ss));}
        else if(cmd=="system") {ONROOT(cmd_system(ss));}
        else {
          if(me==0) sayer("undefined command: "+cmd);
          break;
        }
      } catch(const char* sterr) {
        sayer(sterr);
        loading=false;
        loopbreak=true;
        retval=1;
        break;
      } catch(...) {
        std::cerr<<getpid()<<"unexpected too\n";
        sayer("undefined error");
        loading=false;
        loopbreak=true;
        retval=0;
        break;
      }
    
    }
    
    MPI_Barrier(MPI_COMM_WORLD);
    
    return retval;
  }

  int inputline() {
    int retval=1;
    
    while(retval) {
      char sline[2048];
      if(me==0) {
        std::cin.getline(sline,2047);
        if(std::cin.eof()) sprintf(sline,"exit");

        if(string(sline).substr(0,6)=="@macro") {
          istringstream ms(sline);
          string mname;
          ms>>mname;
          ms>>mname;
          read_macro(std::cin,mname);
          sprintf(sline,"nop");
        }
        

      }

#ifndef DRIFT_NO_MPI
      // broadcast sline
      MPI_Barrier(MPI_COMM_WORLD);
      MPI_Bcast(sline,2048,MPI_CHAR,0,MPI_COMM_WORLD);
#endif
      
      if(strcmp(sline,"nop")) retval=parse(sline);
      
    }
    
    cleanup();
  }
  
};

int main(int narg, char* varg[]) {
  ParamHandler param(narg,varg);
  if(param.exist("--version")) {
    std::cout<<version_string<<std::endl;
    std::cout<<compileid<<std::endl;
    return 0;
  }

#ifdef DRIFT_NO_MPI
  DriftServe FDC;
  FDC.inputline();
#else  
  // mpi initialization
  MPI_Init(&narg,&varg);
  DriftServe FDC;
  FDC.inputline();
  MPI_Finalize();
#endif
  
  return 0;
}

